package com.zcup.miaosha.aop;


import com.google.common.util.concurrent.RateLimiter;
import com.zcup.miaosha.utils.ratelimiter.ExtRateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RateLimiterAop {
    private static final Map<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();

    //定义切入点 com.zcup.miaosha.controller
    // @Pointcut("execution(public * com.zcup.miaosha.controller.*.*(..))")
    @Pointcut("@annotation(extRateLimiter)")
    public void pointcut(ExtRateLimiter extRateLimiter){}

    //使用Aop环绕通知判断拦截所有springmvc请求,判断请求方法上是否存在ExtRateLimiter注解
    @Around(value = "pointcut(extRateLimiter)", argNames = "point,extRateLimiter")
    public Object doBefore(ProceedingJoinPoint point,ExtRateLimiter extRateLimiter) throws Throwable{
        //如果方法上存在@ExtRateLimiter注解
        // Method signatureMethod = getSignatureMethod(point);
        // if (signatureMethod == null) {
        //     return null;
        // }

        //使用反射机制获取拦截方法上自定义的参数
        // ExtRateLimiter extRateLimiter = signatureMethod.getDeclaredAnnotation(ExtRateLimiter.class);

        if (extRateLimiter == null) {
            return point.proceed();
        }

        double permitsPerSecond = extRateLimiter.permitsPerSeconds();
        long timeout = extRateLimiter.timeouts();

        //调用原生的RateLimiter来创建令牌
        RateLimiter rateLimiter = getRateLimiter(permitsPerSecond);

        //获取令牌同中的令牌,如果没有在有效期中获取到令牌的话,则直接用本地服务降级方法,不会进入到时间请求方法中
        boolean b = rateLimiter.tryAcquire(timeout, TimeUnit.MILLISECONDS);
        if (!b){
            fallback();
            return null;
        }
        return point.proceed();
    }

    private RateLimiter getRateLimiter(double permitsPerSecond){
        //获取当前URL
        String requestURI = this.getRequestURI();
        RateLimiter rateLimiter = rateLimiterMap.get(requestURI);
        if (StringUtils.isEmpty(rateLimiter)){
            rateLimiter = RateLimiter.create(permitsPerSecond);
            rateLimiterMap.put(requestURI,rateLimiter);
        }
        return rateLimiter;
    }
    //获取到Aop拦截的方法
    // private Method getSignatureMethod(ProceedingJoinPoint point){
    //     MethodSignature signature = (MethodSignature) point.getSignature();
    //     return signature.getMethod();
    // }

    private HttpServletRequest getRequest(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }

    private String getRequestURI(){
        return getRequest().getRequestURI();
    }

    private void fallback() {
        System.out.println("系统已将请求进行降级处理....");
        //在Aop中获取响应
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = requestAttributes.getResponse();
        response.setHeader("Content-Type","text/html;charset=UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.print("秒杀商品失败,请稍后重试");
        } catch (Exception ignored) {

        }
    }
}
